/**
	Pulsar engine. Source file.
	Standard logging handlers

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

#include <stdio.h>
#include <pulsar/pulsar.h>

/**
	\param formatInt internal message format (libName != NULL)
	\param formatExt external message format (libName == NULL)
	Use following % macro:
	%% - output %
	%m - output message
	%t - output time of message in milliseconds
	%s - output time of message in seconds
	%d - output date and time (DD.MM.YY/HH:MM:SS)
	%n - output unique message number (number)
	%f - output filename
	%# - output line number
	%p - output procedure/function name
	%l - output library name
	%e - output log type as "Fatal", "Error", etc
	%c - output log type as integer (enum LogLevel)
	%i - output INewFrame number
	%h - output thread identifier
	%r - output process identifier
	By default: "%f(%#) : %e : %m"
*/
pl::LogTracerCommon::LogTracerCommon(const pl_char* formatInt, const pl_char* formatExt) :
_formatDefaultInt(formatInt),
_formatDefaultExt(formatExt)
{
	pl_uint32 i;

	for (i=0; i<_formatCustomInt.capacity(); i++)
	{
		_formatCustomInt[i] = NULL;
	}
	for (i=0; i<_formatCustomExt.capacity(); i++)
	{
		_formatCustomExt[i] = NULL;
	}
	return;
}

/**
	Set custom format for different error level
*/
void pl::LogTracerCommon::setCustomFormat(bool extFile, LogLevel level, const pl_char* format)
{
	if (extFile)
	{
		_formatCustomExt[level] = format;
	}
	else
	{
		_formatCustomInt[level] = format;
	}
	return;
}

/**
	Find format to handle the current message
	Internal function
*/
const pl_char* pl::LogTracerCommon::_getDefaultFormat(const pl::LogData &params)
{
	if (params.lib == NULL)
	{
		return (_formatCustomExt[params.level] != NULL) ? _formatCustomExt[params.level] : _formatDefaultExt;
	}
	return (_formatCustomInt[params.level] != NULL) ? _formatCustomInt[params.level] : _formatDefaultInt;
}

/**
	Prepare log message with given format
*/
void pl::LogTracerCommon::_formatLog(const LogData& params, String<1024>& strOutput, const pl_char* format)
{
	if (format == NULL)
	{
		// USe standard
		format = PL_T("%f(%#): %e: %m");
	}
	strOutput.clear();

	while (*format != '\0')
	{
		if (*format != '%')
		{
			strOutput << *(format++);
			continue;
		}

		// Check 2nd char
		switch (*(++format))
		{
			case '%':
				strOutput << PL_T('%');
				break;

			case 'm':
				strOutput << params.msg;
				break;

			case 't':
				strOutput << params.time;
				break;

			case 's':
				strOutput << params.time / 1000.f;
				break;

			case 'n':
				strOutput << params.number;
				break;

			case 'f':
				strOutput << (params.fileName ? params.fileName : PL_T("<NULL>"));
				break;

			case '#':
				strOutput << params.lineNmb;
				break;

			case 'p':
				strOutput << (params.function ? params.function : PL_T("{NULL}"));
				break;

			case 'l':
				strOutput << (params.lib ? params.lib : PL_T("(NULL)"));
				break;

			case 'e':
				if (params.level < LOG_LEVEL_FATAL || params.level > LOG_LEVEL_DEBUG)
				{
					strOutput << params.level;
				}
				else
				{
					strOutput << LogProcessor::errorLevelNames[params.level];
				}
				break;

			case 'c':
				strOutput << params.level;
				break;

			case 'i':
				strOutput << pl::INewFrame::frameCounter;
				break;

			case 'd':
				{
					pl::DateTime dt;
					if (pl::AppBase::appBase->getCurDateTime(dt))
					{
						pl_char datetime[128];
						dt.format(datetime);
						strOutput << datetime;
					}
					break;
				}

			case 'h':
				strOutput << pl::getThreadId();
				break;

			case 'r':
				strOutput << pl::getProcessId();
				break;

			case '\0':
				return;

			default:
				strOutput << PL_T('%');
				break;
		}
		format++;
	}
	return;
}

/**
	Standard tracers.
	Trace log to file
*/
pl::LogTracerFile::LogTracerFile(const Filepath& fileName, const pl_char* formatInt /*= NULL*/, const pl_char* formatExt /*= NULL*/) :
LogTracerCommon(formatInt, formatExt),
_fileHandle(NULL)
{
	_fileHandle = FileManager::openFileWrite(fileName, OPEN_FILE_SHARE_READ + OPEN_FILE_CREATEALWAYS);
#if defined(PL_ANSI)
	// Does nothing
#elif defined(PL_UNICODE) || defined(PL_UNICODE32)
	if (_fileHandle != NULL)
	{
		pl_uint16 value = 0xFEFF;
		_fileHandle->write(value);
	}
#else
#error "Wrong string type"
#endif
	return;
}

/**
	Trace log to file dtor
*/
pl::LogTracerFile::~LogTracerFile()
{
	if (_fileHandle != NULL)
	{
		PL_DELETE _fileHandle;
	}
	return;
}

/**
	Trap log events to file
*/
void pl::LogTracerFile::onLogMessage(const LogData &params)
{
	if (_fileHandle != NULL)
	{
		pl::Lock locker(&_cs);

		String<1024> strOutput;
		_formatLog(params, strOutput, _getDefaultFormat(params));
		strOutput << PL_T("\r\n");
		_fileHandle->writeBuffer(strOutput.get(), sizeof(pl_char), strOutput.size());
	}
	return;
}

/**
	Flush file on disk
*/
void pl::LogTracerFile::onLogFlush()
{
	if (_fileHandle != NULL)
	{
		_fileHandle->flush();
	}
	return;
}

/**
	Standard tracers.
	Trace log to stdout
*/
pl::LogTracerPrintf::LogTracerPrintf(const pl_char* formatInt /*= NULL*/, const pl_char* formatExt /*= NULL*/) :
LogTracerCommon(formatInt, formatExt)
{
	return;
}

/**
	Trap log events to stdout
*/
void pl::LogTracerPrintf::onLogMessage(const LogData &params)
{
	String<1024> strOutput;

	_formatLog(params, strOutput, _getDefaultFormat(params));

	{
		pl::Lock locker(&_cs);
#if defined(PL_ANSI)
		printf("%s\n", strOutput.get());
#elif defined(PL_UNICODE) || defined(PL_UNICODE32)
		printf("%S\n",strOutput.get());
#else
#error "Wrong string type"
#endif
	}
	return;
}

/**
	Flush stdout
*/
void pl::LogTracerPrintf::onLogFlush()
{
	fflush(stdout);
	return;
}

/**
	Standard tracers.
	Trace log to cycle buffer
*/
pl::LogTracerMemory::LogTracerMemory(int bufSize, const pl_char* formatInt /*= NULL*/, const pl_char* formatExt /*= NULL*/) :
LogTracerCommon(formatInt, formatExt),
_q(bufSize, QUEUE_PUSH_MODE_REPLACE),
_enableFlag(true)
{
	return;
}

/**
	Trap log events to cycle buffer
*/
void pl::LogTracerMemory::onLogMessage(const pl::LogData &params)
{
	String<1024> strOutput;

	_formatLog(params, strOutput, _getDefaultFormat(params));
	_q.push(strOutput.get(), strOutput.sizeBytes() + strOutput.sizeChar());
	return;
}

//
// End of file 'pulsar_log_std.cpp'
//
